/******************************************************************************* * Cloud Foundry * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). * You may not use this product except in compliance with the License. * * This product includes a number of subcomponents with * separate copyright notices and license terms. Your use of these * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ package org.cloudfoundry.identity.uaa.mock.token; import org.apache.commons.codec.binary.Base64; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.MapCollector; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; import org.cloudfoundry.identity.uaa.zone.TokenPolicy; import org.junit.Before; import org.junit.Test; import org.springframework.http.MediaType; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.security.oauth2.provider.ClientRegistrationService; import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.test.web.servlet.MvcResult; import java.util.Collections; import java.util.List; import java.util.Map; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.collection.IsMapContaining.hasKey; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public class TokenKeyEndpointMockMvcTests extends InjectedMockContextTest { private static final String signKey = "-----BEGIN RSA PRIVATE KEY-----\n" + "MIIEowIBAAKCAQEA0m59l2u9iDnMbrXHfqkOrn2dVQ3vfBJqcDuFUK03d+1PZGbV\n" + "lNCqnkpIJ8syFppW8ljnWweP7+LiWpRoz0I7fYb3d8TjhV86Y997Fl4DBrxgM6KT\n" + "JOuE/uxnoDhZQ14LgOU2ckXjOzOdTsnGMKQBLCl0vpcXBtFLMaSbpv1ozi8h7DJy\n" + "VZ6EnFQZUWGdgTMhDrmqevfx95U/16c5WBDOkqwIn7Glry9n9Suxygbf8g5AzpWc\n" + "usZgDLIIZ7JTUldBb8qU2a0Dl4mvLZOn4wPojfj9Cw2QICsc5+Pwf21fP+hzf+1W\n" + "SRHbnYv8uanRO0gZ8ekGaghM/2H6gqJbo2nIJwIDAQABAoIBAHPV9rSfzllq16op\n" + "zoNetIJBC5aCcU4vJQBbA2wBrgMKUyXFpdSheQphgY7GP/BJTYtifRiS9RzsHAYY\n" + "pAlTQEQ9Q4RekZAdd5r6rlsFrUzL7Xj/CVjNfQyHPhPocNqwrkxp4KrO5eL06qcw\n" + "UzT7UtnoiCdSLI7IL0hIgJZP8J1uPNdXH+kkDEHE9xzU1q0vsi8nBLlim+ioYfEa\n" + "Q/Q/ovMNviLKVs+ZUz+wayglDbCzsevuU+dh3Gmfc98DJw6n6iClpd4fDPqvhxUO\n" + "BDeQT1mFeHxexDse/kH9nygxT6E4wlU1sw0TQANcT6sHReyHT1TlwnWlCQzoR3l2\n" + "RmkzUsECgYEA8W/VIkfyYdUd5ri+yJ3iLdYF2tDvkiuzVmJeA5AK2KO1fNc7cSPK\n" + "/sShHruc0WWZKWiR8Tp3d1XwA2rHMFHwC78RsTds+NpROs3Ya5sWd5mvmpEBbL+z\n" + "cl3AU9NLHVvsZjogmgI9HIMTTl4ld7GDsFMt0qlCDztqG6W/iguQCx8CgYEA3x/j\n" + "UkP45/PaFWd5c1DkWvmfmi9UxrIM7KeyBtDExGIkffwBMWFMCWm9DODw14bpnqAA\n" + "jH5AhQCzVYaXIdp12b+1+eOOckYHwzjWOFpJ3nLgNK3wi067jVp0N0UfgV5nfYw/\n" + "+YoHfYRCGsM91fowh7wLcyPPwmSAbQAKwbOZKfkCgYEAnccDdZ+m2iA3pitdIiVr\n" + "RaDzuoeHx/IfBHjMD2/2ZpS1aZwOEGXfppZA5KCeXokSimj31rjqkWXrr4/8E6u4\n" + "PzTiDvm1kPq60r7qi4eSKx6YD15rm/G7ByYVJbKTB+CmoDekToDgBt3xo+kKeyna\n" + "cUQqUdyieunM8bxja4ca3ukCgYAfrDAhomJ30qa3eRvFYcs4msysH2HiXq30/g0I\n" + "aKQ12FSjyZ0FvHEFuQvMAzZM8erByKarStSvzJyoXFWhyZgHE+6qDUJQOF6ruKq4\n" + "DyEDQb1P3Q0TSVbYRunOWrKRM6xvJvSB4LUVfSvBDsv9TumKqwfZDVFVn9yXHHVq\n" + "b6sjSQKBgDkcyYkAjpOHoG3XKMw06OE4OKpP9N6qU8uZOuA8ZF9ZyR7vFf4bCsKv\n" + "QH+xY/4h8tgL+eASz5QWhj8DItm8wYGI5lKJr8f36jk0JLPUXODyDAeN6ekXY9LI\n" + "fudkijw0dnh28LJqbkFF5wLNtATzyCfzjp+czrPMn9uqLNKt/iVD\n" + "-----END RSA PRIVATE KEY-----"; private static final String verifyKey = "-----BEGIN PUBLIC KEY-----\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0m59l2u9iDnMbrXHfqkO\n" + "rn2dVQ3vfBJqcDuFUK03d+1PZGbVlNCqnkpIJ8syFppW8ljnWweP7+LiWpRoz0I7\n" + "fYb3d8TjhV86Y997Fl4DBrxgM6KTJOuE/uxnoDhZQ14LgOU2ckXjOzOdTsnGMKQB\n" + "LCl0vpcXBtFLMaSbpv1ozi8h7DJyVZ6EnFQZUWGdgTMhDrmqevfx95U/16c5WBDO\n" + "kqwIn7Glry9n9Suxygbf8g5AzpWcusZgDLIIZ7JTUldBb8qU2a0Dl4mvLZOn4wPo\n" + "jfj9Cw2QICsc5+Pwf21fP+hzf+1WSRHbnYv8uanRO0gZ8ekGaghM/2H6gqJbo2nI\n" + "JwIDAQAB\n" + "-----END PUBLIC KEY-----"; @Before public void setUp() throws Exception { setUp(signKey); } public void setUp(String signKey) throws Exception { IdentityZoneProvisioning provisioning = getWebApplicationContext().getBean(IdentityZoneProvisioning.class); IdentityZone uaa = provisioning.retrieve("uaa"); TokenPolicy tokenPolicy = new TokenPolicy(); tokenPolicy.setKeys(Collections.singletonMap("testKey", signKey)); uaa.getConfig().setTokenPolicy(tokenPolicy); provisioning.update(uaa); } @Test public void checkTokenKeyValues() throws Exception { String basicDigestHeaderValue = "Basic " + new String(Base64.encodeBase64(("app:appclientsecret").getBytes())); MvcResult result = getMockMvc().perform( get("/token_key") .accept(MediaType.APPLICATION_JSON) .header("Authorization", basicDigestHeaderValue)) .andExpect(status().isOk()) .andReturn(); Map<String, Object> key = JsonUtils.readValue(result.getResponse().getContentAsString(), Map.class); validateKey(key); } @Test public void get_token_asymmetric_but_authenticated() throws Exception { BaseClientDetails client = new BaseClientDetails(new RandomValueStringGenerator().generate(), "", "foo,bar", "client_credentials,password", "uaa.none"); client.setClientSecret("secret"); getWebApplicationContext().getBean(ClientRegistrationService.class).addClientDetails(client); String basicDigestHeaderValue = "Basic " + new String(Base64.encodeBase64((client.getClientId()+":secret").getBytes())); MvcResult result = getMockMvc().perform( get("/token_key") .accept(MediaType.APPLICATION_JSON) .header("Authorization", basicDigestHeaderValue)) .andExpect(status().isOk()) .andReturn(); Map<String, Object> key = JsonUtils.readValue(result.getResponse().getContentAsString(), Map.class); validateKey(key); } @Test public void get_token_symmetric_authenticated_but_missing_scope() throws Exception { setUp("key"); try { BaseClientDetails client = new BaseClientDetails(new RandomValueStringGenerator().generate(), "", "foo,bar", "client_credentials,password", "uaa.none"); client.setClientSecret("secret"); getWebApplicationContext().getBean(ClientRegistrationService.class).addClientDetails(client); String basicDigestHeaderValue = "Basic " + new String(Base64.encodeBase64((client.getClientId() + ":secret").getBytes())); getMockMvc().perform( get("/token_key") .accept(MediaType.APPLICATION_JSON) .header("Authorization", basicDigestHeaderValue)) .andExpect(status().isForbidden()) .andReturn(); } finally { setUp(signKey); } } @Test public void checkTokenKeyValuesAnonymous() throws Exception { MvcResult result = getMockMvc().perform( get("/token_key") .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); Map<String, Object> key = JsonUtils.readValue(result.getResponse().getContentAsString(), Map.class); validateKey(key); } @Test public void checkTokenKeysValues() throws Exception { String basicDigestHeaderValue = "Basic " + new String(Base64.encodeBase64(("app:appclientsecret").getBytes())); MvcResult result = getMockMvc().perform( get("/token_keys") .accept(MediaType.APPLICATION_JSON) .header("Authorization", basicDigestHeaderValue)) .andExpect(status().isOk()) .andReturn(); Map<String, Object> keys = JsonUtils.readValue(result.getResponse().getContentAsString(), Map.class); validateKeys(keys); } @Test public void checkTokenKeysValuesAnonymous() throws Exception { MvcResult result = getMockMvc().perform( get("/token_keys") .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andDo(print()) .andReturn(); Map<String, Object> keys = JsonUtils.readValue(result.getResponse().getContentAsString(), Map.class); validateKeys(keys); } public void validateKey(Map<String,Object> key) { Object kty = key.get("kty"); assertNotNull(kty); assertTrue(kty instanceof String); assertEquals("RSA", kty); Object use = key.get("use"); //optional //values for use are //1. sig - key used to verify the signature //2. enc - key used to assertNotNull(use); assertTrue(use instanceof String); assertEquals("sig", use); Object key_ops = key.get("key_ops"); //an String[] containing values like //sign, verify, encrypt, decrypt, wrapKey, unwrapKey, deriveKey, deriveBits //should not be used together with 'use' (mutually exclusive) assertNull(key_ops); Object alg = key.get("alg"); //optional - algorithm of key assertNotNull(alg); assertTrue(alg instanceof String); assertEquals("RS256", alg); Object kid = key.get("kid"); //optional - indicates the id for a certain key //single key doesn't need one assertEquals("testKey", kid); Object x5u = key.get("x5u"); //optional - URL that points to a X.509 key or certificate assertNull(x5u); Object x5c = key.get("x5c"); //optional - contains a chain of one or more //PKIX certificate assertNull(x5c); Object x5t = key.get("x5t"); //optional - x509 certificate SHA-1 assertNull(x5t); Object x5tHashS256 = key.get("x5t#S256"); //optional assertNull(x5tHashS256); Object actual = key.get("value"); assertNotNull(actual); assertTrue(actual instanceof String); assertEquals(verifyKey, actual); Object e = key.get("e"); assertNotNull(e); assertTrue(e instanceof String); assertEquals("AQAB", e); isUrlSafeBase64((String) e); Object n = key.get("n"); assertNotNull(n); assertTrue(n instanceof String); isUrlSafeBase64((String) n); } public void validateKeys(Map<String, Object> response) { List<Map<String, Object>> keys = (List<Map<String, Object>>)response.get("keys"); assertNotNull(keys); Map<String, ? extends Map<String, Object>> keysMap = keys.stream().collect(new MapCollector<>(k -> (String) k.get("kid"), k -> k)); assertThat(keysMap, hasKey(is("testKey"))); validateKey(keysMap.get("testKey")); } private void isUrlSafeBase64(String base64) { java.util.Base64.Encoder encoder = java.util.Base64.getUrlEncoder().withoutPadding(); java.util.Base64.Decoder decoder = java.util.Base64.getUrlDecoder(); assertEquals(base64, encoder.encodeToString(decoder.decode(base64))); } }